package endecode

import (
	"crypto/sha1"
	"errors"
	"golang.org/x/crypto/pbkdf2"
	"reflect"
	"sync"
)

func NewCryptor(crypt, key, salt string) *Cryptor {
	return &Cryptor{
		Crypt: crypt,
		Key:   key,
		Salt:  salt,
	}
}

type Cryptor struct {
	Crypt string
	Key   string
	Salt  string
}

var endeCode EnDeCode
var mu sync.Mutex

type EnDeCode interface {
	BlockCrypt
}

func Encode(dst []byte, cryptor *Cryptor) ([]byte, error) {
	err := GetCrypt(cryptor)
	if err != nil {
		return nil, err
	}

	return endeCode.Encrypt(dst, dst), nil
}
func Decode(dst []byte, cryptor *Cryptor) ([]byte, error) {
	err := GetCrypt(cryptor)
	if err != nil {
		return nil, err
	}

	return endeCode.Decrypt(dst, dst), nil
}
func GetCrypt(cryptor *Cryptor) error {
	if reflect.ValueOf(cryptor).IsNil() {
		return errors.New("cryptor is null")
	}

	if endeCode != nil {
		return nil
	}

	mu.Lock()
	defer mu.Unlock()

	if endeCode != nil {
		return nil
	}

	pass := pbkdf2.Key([]byte(cryptor.Key), []byte(cryptor.Salt), 4096, 32, sha1.New)

	var crypt EnDeCode
	var err error

	switch cryptor.Crypt {
	case "tea":
		crypt, err = NewTEABlockCrypt(pass[:16])
	case "xor":
		crypt, err = NewSimpleXORBlockCrypt(pass)
	case "none":
		crypt, err = NewNoneBlockCrypt(pass)
	case "aes-128":
		crypt, err = NewAESBlockCrypt(pass[:16])
	case "aes-192":
		crypt, err = NewAESBlockCrypt(pass[:24])
	case "blowfish":
		crypt, err = NewBlowfishBlockCrypt(pass)
	case "twofish":
		crypt, err = NewTwofishBlockCrypt(pass)
	case "cast5":
		crypt, err = NewCast5BlockCrypt(pass[:16])
	case "3des":
		crypt, err = NewTripleDESBlockCrypt(pass[:24])
	case "xtea":
		crypt, err = NewXTEABlockCrypt(pass[:16])
	case "salsa20":
		crypt, err = NewSalsa20BlockCrypt(pass)
	// case "chacha20":
	// 	crypt, err = NewChacha20BlockCrypt(pass)
	default:
		cryptor.Crypt = "aes"
		crypt, err = NewAESBlockCrypt(pass)
	}

	if nil != err {
		return err
	}

	endeCode = crypt

	return nil
}
